/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Microsoft Public License. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If 
 * you cannot locate the  Microsoft Public License, please send an email to 
 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Microsoft Public License.
 *
 * You must not remove this notice, or any other, from this software.
 *
 * THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 * KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
 * PARTICULAR PURPOSE.
 * ***************************************************************************/


#include "stdafx.h"
#include <pgl/igfx/GDIpHelper.h>

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

namespace gfxinterface
{
    using namespace Gdiplus;

    long CGDIpInitializer::m_initcount=0;  

    CGDIpHelper::CGDIpHelper(CGDIpInterface* pGfx)
    : m_pGfx(pGfx) 
    , m_bTripleBuffering(false)
    {

    }

    CGDIpHelper::~CGDIpHelper()
    {
        Destroy();
    }

    void CGDIpHelper::Destroy()
    {
        SetDirty();
        m_pGfx=NULL;
    }

    void CGDIpHelper::SetFrontDirty()
    {
        CSingleLock synchro(&m_ThreadLock);
        // locking thread
        synchro.Lock();
        if (synchro.IsLocked())
        {
#if defined( _DEBUG )
#undef new
#endif
            m_pMemBitmapFront.reset();
            m_pCachedBitmap.reset();
#if defined( _DEBUG )
#define new DEBUG_NEW
#endif

            synchro.Unlock();
        }
    }

    void CGDIpHelper::SetCacheDirty()
    {
        CSingleLock synchro(&m_ThreadLock);
        // locking thread
        synchro.Lock();
        if (synchro.IsLocked())
        {
#if defined( _DEBUG )
#undef new
#endif
            m_pCachedBitmap.reset();
#if defined( _DEBUG )
#define new DEBUG_NEW
#endif

            synchro.Unlock();
        }
    }

    void CGDIpHelper::SetDirty()
    {
        CSingleLock synchro(&m_ThreadLock);
        // locking thread
        synchro.Lock();
        if (synchro.IsLocked())
        {
#if defined( _DEBUG )
#undef new
#endif
            m_pMemGraphics.reset();
            m_pMemBitmap.reset();
            m_pMemBitmapFront.reset();
            m_pCachedBitmap.reset();
#if defined( _DEBUG )
#define new DEBUG_NEW
#endif
            synchro.Unlock();
        }
    }

    void CGDIpHelper::Draw(const CRect& rect, HDC hDC, Graphics *pGraphics)
    {
        using namespace gfxinterface;

        if (!pGraphics)
            throw std::exception("Graphics not set in GdipHelper");

        CSingleLock synchro(&m_ThreadLock);
        // locking thread
        synchro.Lock();
        if (synchro.IsLocked())
        {
            try
            {
                // draw from cached bitmap to window
                if(!m_pCachedBitmap)
                {
                    if (!m_bTripleBuffering)
                    {
                        //! if bitmap destroyed, rebuild
                        if (!m_pMemBitmap)
                            CreateOffScreenBitmap(rect.Width(), rect.Height(),  hDC, pGraphics, LayerDisabled);

                        // create cached bitmap
                        CreateCachedBitmap(pGraphics);
                    }
                    else
                    {
                        //! if bitmap destroyed, rebuild
                        if (!m_pMemBitmap)
                            CreateOffScreenBitmap(rect.Width(), rect.Height(),  hDC, pGraphics, LayerBack);

                        if (!m_pMemBitmapFront)
                            CreateOffScreenBitmap(rect.Width(), rect.Height(),  hDC, pGraphics, LayerFront);

                        // create cached bitmap
                        CreateCachedBitmap(pGraphics);
                    }
                }

                // draw cached bitmap
                pGraphics->DrawCachedBitmap(m_pCachedBitmap.get(), rect.left, rect.top);
                synchro.Unlock();
            }
            catch(std::exception const& e)
            {
                synchro.Unlock();
                throw e;
            }
        }
    }



    void CGDIpHelper::CreateOffScreenBitmap(int nWidth, int nHeight, HDC hDC, Graphics *pGraphics, ELayer eLayer)
    {
        using namespace Gdiplus;
        using namespace gfxinterface;

        if(!nWidth || !nHeight)
            return; //don't have anything to do. 

#if defined( _DEBUG )
#undef new
#endif

        if (eLayer == LayerBack || eLayer == LayerDisabled)
        {
            // create off-screen bitmap
            m_pMemBitmap.reset(new Bitmap(nWidth, nHeight));
            // create off-screen graphics
            m_pMemGraphics.reset(Graphics::FromImage(m_pMemBitmap.get()));
        }
        else if (eLayer == LayerFront)
        {
            m_pMemGraphics.reset();
            if (!m_pMemBitmap)
                throw std::exception("Memory bitmap should not be null");

            // create off-screen bitmap, front
            m_pMemBitmapFront.reset(
                m_pMemBitmap->Clone(
                    0,0, 
                    m_pMemBitmap->GetWidth(), 
                    m_pMemBitmap->GetHeight(), 
                    m_pMemBitmap->GetPixelFormat()
                    )
                );

            // create off-screen graphics
            m_pMemGraphics.reset(Graphics::FromImage(m_pMemBitmapFront.get()));
        }

#if defined( _DEBUG )
#define new DEBUG_NEW
#endif


        ASSERT(m_pGfx);
        m_pGfx->SetGraphics(m_pMemGraphics.get());
        m_pGfx->BeginPaint(hDC);
        m_pGfx->SetLayer(eLayer);
        // Draw....
        DrawOffScreen(eLayer);
        m_pGfx->EndPaint();
    }

    void CGDIpHelper::CreateCachedBitmap(Graphics* pGraphics)
    {
        if(!pGraphics)
            throw std::exception("Graphics pointer is null");

        ASSERT(m_pMemBitmap);
#if defined( _DEBUG )
#undef new
#endif

        if (! m_bTripleBuffering)
        {
            // now create the cached bitmap
            m_pCachedBitmap.reset(new CachedBitmap(m_pMemBitmap.get(), pGraphics));
        }
        else
        {
            // now create the cached bitmap
            m_pCachedBitmap.reset(new CachedBitmap(m_pMemBitmapFront.get(), pGraphics));
        }

#if defined( _DEBUG )
#define new DEBUG_NEW
#endif
    }

    CGDIpInitializer::CGDIpInitializer(bool bInitCtorDtor) 
        : m_bInitCtorDtor(bInitCtorDtor), 
        m_bInited(false), m_hMap(NULL), m_gdiplusToken(NULL), 
        m_gdiplusStartupInput(NULL),
        m_sGDIpInitedWithSuccess(Gdiplus::GenericError)
    {
        if (m_bInitCtorDtor) 
        {
            Initialize();
        }
    }

    CGDIpInitializer::~CGDIpInitializer()
    {
        if (m_bInitCtorDtor) 
        {
            Deinitialize();
        }
    }

    bool CGDIpInitializer::Initialize()
    {
        if (!m_bInited) 
        {
            TCHAR buffer[1024];
            _stprintf(buffer, _T("GDIPlusInitID=%x"), GetCurrentProcessId());
            m_hMap = CreateFileMapping((HANDLE) INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE | SEC_COMMIT, 0, sizeof(long), buffer);
            if (m_hMap != NULL) 
            {
                // We might have a winner
                if (GetLastError() == ERROR_ALREADY_EXISTS) 
                { 
                    CloseHandle(m_hMap); 
                } 
                else 
                {
                    // Yes, we have a winner
                    m_bInited = true;
                    m_sGDIpInitedWithSuccess=Gdiplus::GdiplusStartup(&m_gdiplusToken, &m_gdiplusStartupInput, NULL);
                    TRACE(_T("Initialized GDI+\n"));
                }
            }
        }
        m_initcount++;

        return IsInitialized();
    }

    void CGDIpInitializer::Deinitialize()
    {
        m_initcount--;
        if (m_bInited && m_initcount == 0) 
        {
            TRACE(_T("GDI+ shutdown\n"));
            Gdiplus::GdiplusShutdown(m_gdiplusToken);
            CloseHandle(m_hMap);
            m_bInited = false;
        }
    }

    void CGDIpHelper::GetImageEncodersList( std::vector<std::_tstring>& encoders )
    {
        using namespace Gdiplus;
        USES_CONVERSION;

        UINT  num;        // number of image encoders
        UINT  size;       // size, in bytes, of the image encoder array

        ImageCodecInfo* pImageCodecInfo;

        // How many encoders are there?
        // How big (in bytes) is the array of all ImageCodecInfo objects?
        GetImageEncodersSize(&num, &size);

        // Create a buffer large enough to hold the array of ImageCodecInfo
        // objects that will be returned by GetImageEncoders.
        pImageCodecInfo = (ImageCodecInfo*)(malloc(size));

        // GetImageEncoders creates an array of ImageCodecInfo objects
        // and copies that array into a previously allocated buffer. 
        // The third argument, imageCodecInfo, is a pointer to that buffer. 
        GetImageEncoders(num, size, pImageCodecInfo);

        // Display the graphics file format (MimeType)
        // for each ImageCodecInfo object.
        encoders.resize(num);
        for(UINT j = 0; j < num; ++j)
        { 
#ifdef _UNICODE
            encoders[j]=pImageCodecInfo[j].MimeType;
#else
            encoders[j]=W2A(pImageCodecInfo[j].MimeType);
#endif
        }

        free(pImageCodecInfo);
    }

    void CGDIpHelper::GetImageDecodersList( std::vector<std::_tstring>& decoders )
    {
        using namespace Gdiplus;
        USES_CONVERSION;

        UINT  num;        // number of image encoders
        UINT  size;       // size, in bytes, of the image encoder array

        ImageCodecInfo* pImageCodecInfo;

        // How many encoders are there?
        // How big (in bytes) is the array of all ImageCodecInfo objects?
        GetImageDecodersSize(&num, &size);

        // Create a buffer large enough to hold the array of ImageCodecInfo
        // objects that will be returned by GetImageEncoders.
        pImageCodecInfo = (ImageCodecInfo*)(malloc(size));

        // GetImageEncoders creates an array of ImageCodecInfo objects
        // and copies that array into a previously allocated buffer. 
        // The third argument, imageCodecInfo, is a pointer to that buffer. 
        GetImageDecoders(num, size, pImageCodecInfo);

        // Display the graphics file format (MimeType)
        // for each ImageCodecInfo object.
        decoders.resize(num);
        for(UINT j = 0; j < num; ++j)
        { 
#ifdef _UNICODE
            decoders[j]=pImageCodecInfo[j].MimeType;
#else
            decoders[j]=W2A(pImageCodecInfo[j].MimeType);
#endif
        }

        free(pImageCodecInfo);
    }

    int CGDIpHelper::GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
    {
        UINT  num = 0;          // number of image encoders
        UINT  size = 0;         // size of the image encoder array in bytes

        Gdiplus::ImageCodecInfo* pImageCodecInfo = NULL;

        Gdiplus::GetImageEncodersSize(&num, &size);
        if(size == 0)
            return -1;  // Failure

        pImageCodecInfo = (Gdiplus::ImageCodecInfo*)(malloc(size));
        if(pImageCodecInfo == NULL)
            return -1;  // Failure

        Gdiplus::GetImageEncoders(num, size, pImageCodecInfo);

        for(UINT j = 0; j < num; ++j)
        {
            if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 )
            {
                *pClsid = pImageCodecInfo[j].Clsid;
                free(pImageCodecInfo);
                return j;  // Success
            }    
        }

        free(pImageCodecInfo);
        return -1;  // Failure
    }

    void CGDIpHelper::GetStatusString(Gdiplus::Status status, std::_tstring& strStatus, std::_tstring& strDescription)
    {
        using namespace Gdiplus;

        switch(status)
        {
        case Ok:
            strStatus=_T("Ok");
            strDescription=_T("Indicates that the method call was successful.");
            break;
        case GenericError:
            strStatus=_T("GenericError");
            strDescription=_T("Indicates that there was an error on the method call, which is identified as something other than those defined by the other elements of this enumeration.");
            break;
        case InvalidParameter:
            strStatus=_T("InvalidParameter");
            strDescription=_T("Indicates that one of the arguments passed to the method was not valid.");
            break;
        case OutOfMemory:
            strStatus=_T("OutOfMemory");
            strDescription=_T("Indicates that the operating system is out of memory and could not allocate memory to process the method call. For an explanation of how constructors use the OutOfMemory status, see the Remarks section at the end of this topic.");
            break;
        case ObjectBusy:
            strStatus=_T("ObjectBusy");
            strDescription=_T("Indicates that one of the arguments specified in the API call is already in use in another thread.");
            break;
        case InsufficientBuffer:
            strStatus=_T("InsufficientBuffer");
            strDescription=_T("Indicates that a buffer specified as an argument in the API call is not large enough to hold the data to be received.");
            break;
        case NotImplemented:
            strStatus=_T("NotImplemented");
            strDescription=_T("Indicates that the method is not implemented.");
            break;
        case Win32Error:
            strStatus=_T("Win32Error");
            strDescription=_T("Indicates that the method generated a Win32Error.");
            break;
        case WrongState:
            strStatus=_T("WrongState");
            strDescription=_T("Indicates that the object is in an invalid state to satisfy the API call. For example, calling GetColor from a pen that isn't a single, solid color results in a WrongState status.");
            break;
        case Aborted:
            strStatus=_T("Aborted");
            strDescription=_T("Indicates that the method was aborted.");
            break;
        case FileNotFound:
            strStatus=_T("FileNotFound");
            strDescription=_T("Indicates that the specified image file or metafile cannot be found.");
            break;
        case ValueOverflow:
            strStatus=_T("ValueOverflow");
            strDescription=_T("Indicates that the method performed an arithmetic operation that produced a numeric overflow.");
            break;
        case AccessDenied:
            strStatus=_T("AccessDenied");
            strDescription=_T("Indicates that a write operation is not allowed on the specified file.");
            break;
        case UnknownImageFormat:
            strStatus=_T("UnknownImageFormat");
            strDescription=_T("Indicates that the specified image file format is not known.");
            break;
        case FontFamilyNotFound:
            strStatus=_T("FontFamilyNotFound");
            strDescription=_T("Indicates that the specified font family cannot be found. Either the font family name is incorrect or the font family is not installed.");
            break;
        case FontStyleNotFound:
            strStatus=_T("FontStyleNotFound");
            strDescription=_T("Indicates that the specified style is not available for the specified font family.");
            break;
        case NotTrueTypeFont:
            strStatus=_T("NotTrueTypeFont");
            strDescription=_T("Indicates that the font retrieved from an HDC or LOGFONT is not a TrueType font and cannot be used with GDI+.");
            break;
        case UnsupportedGdiplusVersion:
            strStatus=_T("UnsupportedGdiplusVersion");
            strDescription=_T("Indicates that the version of GDI+ that is installed on the system is incompatible with the version with which the application was compiled.");
            break;
        case GdiplusNotInitialized:
            strStatus=_T("GdiplusNotInitialized");
            strDescription=_T("Indicates that the GDI+ API is not in an initialized state. To function, all GDI+ objects require that GDI+ be in an initialized state. Initialize GDI+ by calling GdiplusStartup.");
            break;
        case PropertyNotFound:
            strStatus=_T("PropertyNotFound");
            strDescription=_T("Indicates that the specified property does not exist in the image.");
            break;
        case PropertyNotSupported:
            strStatus=_T("PropertyNotSupported");
            strDescription=_T("Indicates that the specified property is not supported by the format of the image and, therefore, cannot be set.");
            break;
        default:
            strStatus=_T("Error code not found.");
            strDescription=_T("");
        }
    }


};
